/*
 * COPYRIGHT:	2010 Christopher Smith, licenced under the GNU GPL v2; see COPYING in the top level directory
 * PROJECT:		sdedit
 * FILE:		ntds ldap lookup guid.cpp
 * PURPOSE:		NTDS over LDAP support
 *				ntds type class
 */

namespace Ldap
{

void InitSecurityInformationControl(LDAPControl &Ctrl OUT, auto_free<BERVAL, &free_BERVAL> &CtrlData OUT, SECURITY_INFORMATION Si)
{
	{
		auto_free<BerElement, free_BerElement1> TempBer = auto_take_(ber_alloc_t(LBER_USE_DER)); //ber_free(pber,1);

		//Format and encode the SEQUENCE data in a BerElement
		ber_printf(TempBer, "{i}", Si);

		//Transfer the encoded data into a BERVAL
		RaiseOnTextErrorT(ber_flatten(TempBer, CtrlData.assign_indirect()), -1, L"ber_flatten failed");
	}

	//init Ctrl
	Ctrl.ldctl_oid = LDAP_SERVER_SD_FLAGS_OID_W;
	Ctrl.ldctl_iscritical = TRUE;
	Ctrl.ldctl_value.bv_val = CtrlData->bv_val;
	Ctrl.ldctl_value.bv_len = CtrlData->bv_len;
}

auto_take<SECURITY_DESCRIPTOR_RELATIVE> GetObjectSd(LdapConnection *lc, PCWSTR dn, SECURITY_INFORMATION Si)
{
	PWSTR Attrs[] = {L"nTSecurityDescriptor", NULL};

	LDAPControl SiControl;
	LDAPControl *ServerControls[] = {&SiControl, NULL};
	auto_free<BERVAL, &free_BERVAL> SiCtrlData;
	InitSecurityInformationControl(SiControl, SiCtrlData, Si);

	ULONG Msg = StartLdapSearchEx(lc, LDAP_SCOPE_BASE, Attrs, ServerControls, NULL, dn, SbtLiteral, L"(objectClass=*)");

	auto_szpod<SECURITY_DESCRIPTOR_RELATIVE> Ret = AwaitSingleSearchResult<SECURITY_DESCRIPTOR_RELATIVE>(lc, Msg, L"nTSecurityDescriptor");

	if(Ret)
		return Ret.release();
	else
		RaiseLdapError(LDAP_NO_SUCH_OBJECT);

	return auto_take<SECURITY_DESCRIPTOR_RELATIVE>(NULL);
}

void SetObjectSd(LdapConnection *lc, PCWSTR dn, SECURITY_DESCRIPTOR_RELATIVE *Sd, SECURITY_INFORMATION Si)
{
	berval Val = {narrowing_cast<ULONG>(auto_szpod_length(Sd)), c_cast<PCHAR>(Sd)};
	berval *Vals[] = {&Val, NULL};
	
	LDAPMod Mod = {LDAP_MOD_REPLACE | LDAP_MOD_BVALUES, L"nTSecurityDescriptor", NULL};
	LDAPMod *Mods[] = {&Mod, NULL};
	Mods[0]->mod_vals.modv_bvals = Vals;

	LDAPControl SiControl;
	LDAPControl *ServerControls[] = {&SiControl, NULL};
	auto_free<BERVAL, &free_BERVAL> SiCtrlData;
	InitSecurityInformationControl(SiControl, SiCtrlData, Si);

	LcWin32ServerErrorLock(lc, TRUE); //prevent others from overriding the value of LDAP_OPT_SERVER_EXT_ERROR

	if(ldap_modify_ext_s(lc->ld, const_cast<PWSTR>(dn), Mods, ServerControls, NULL) != LDAP_SUCCESS)
	{
		ULONG Win32Error;

		ldap_get_option(lc->ld, LDAP_OPT_SERVER_EXT_ERROR, &Win32Error);

		RaiseWin32Error(Win32Error);
	}
}

using namespace ObjectTypes;

namespace Specific
{

PCWSTR NtdsObject[DESC_SPECIFIC_SIZE] = 
{
	L"Create Child",
	L"Delete Child",
	L"List Children",
	L"Self",
	L"Read Property",
	L"Write Property",
	L"Delete Tree",
	L"List Object",
	L"Extended"
};

GENERIC_MAPPING NtdsGenericMapping = 
{
	DS_GENERIC_READ,
	DS_GENERIC_WRITE,
	DS_GENERIC_EXECUTE,
	DS_GENERIC_ALL
};

}//namespace Specific

void TruncateDomainComponents(PWSTR Dn IN OUT, PCWSTR DomainDc IN)
{
	SIZE_T DnLen = wcslen(Dn);
	SIZE_T DomainDcLen = wcslen(DomainDc);

	if(DnLen <= (DomainDcLen + 1)) //the +1 is for a comma to seperate the DCs from the rest
		RaiseTextError(L"Distinguished name found without appropriate domain components", L"truncating domain components", Dn);

	Dn[DnLen - (DomainDcLen + 1)] = 0; //position of domain DC set's seperating comma
}

class NtdsObject : public Win32::OpaqueAccessNtSecDescObject, public ContainerObject
{
	auto_refct<LdapConnection> Lc;

public:
	NtdsObject(LdapConnection *Lc, PCWSTR Dn) :
		Object(Specific::NtdsObject, Dn),
		OpaqueAccessNtSecDescObject(SD_FLAG_OBJECT_SUPPORT | SD_FLAG_NO_SYNCHRONIZE | SD_FLAG_CONTAINERS_ONLY),
		ContainerObject(Specific::NtdsGenericMapping),
		Lc(Lc)
	{
		if(Dn && Dn[0])
		{
			auto_free<PWSTR, free_ldap_value> PathParts2 = auto_take_(ldap_explode_dn(const_cast<WCHAR *const>(Name.get()), FALSE));
			
			SIZE_T PartCt = 0;
			PWSTR *Part = PathParts2;

			//count number of elements
			while(*Part++)
				PartCt++;

			//ensure capacity to avoid unncessary resizing
			PathComponents.increase_maxct(PartCt);

			//reverse from DN's little endian order to PathComponent's big endian order
			for(SIZE_T i = PartCt; i--;)
				*PathComponents.next() = PathParts2[i];
		}
	}
	
	virtual LONG QuerySecDesc(SECURITY_INFORMATION Si, OUT auto_szpod<SECURITY_DESCRIPTOR_RELATIVE> &Sd) override
	{
		return (Sd = GetObjectSd(Lc, Name, Si)) ? ERROR_SUCCESS : ERROR_ACCESS_DENIED;
	}

	virtual auto_take<SECURITY_DESCRIPTOR_RELATIVE> QuerySecDesc(SECURITY_INFORMATION Si) override
	{
		return GetObjectSd(Lc, Name, Si);
	}

	void SetSecDesc(IN SECURITY_DESCRIPTOR const *Sd, IN SECURITY_INFORMATION Si) override
	{
		SetObjectSd(Lc, Name, c_cast<SECURITY_DESCRIPTOR_RELATIVE *>(Sd), Si);
	}

	virtual Object *OpenParent(UCHAR Privileges) override
	{
		if(!Name || !Name[0])
			return NULL; //no name == no parent

		auto_buffer ParentDn;
		BOOLEAN FirstElement = TRUE;

		//DNs are little-endian, but PathComponents is big endian:
		//to truncate the least significant element,
		//start appending in reverse order at second-to-last component
		for(SIZE_T i = PathComponents.get_ct() - 1; i--;)
		{
			if(!FirstElement) //only need commas after first element
				ParentDn.append(L",", sizeof(WCHAR));
			else
				FirstElement = FALSE;

			ParentDn.append(PathComponents[i].get(), wcslen(PathComponents[i]) * sizeof(WCHAR));
		}

		ParentDn.append(L"\0", sizeof(WCHAR));

		return new NtdsObject(Lc, ParentDn.get_as<WCHAR>());
	}

	virtual void WalkChildren(IN ObjectTypes::ChildrenWalkDest &Dest, UCHAR Privileges) override
	{
		PWSTR Attrs[] = {L"distinguishedName", NULL};

		ULONG OpNum = StartLdapSearch(Lc, LDAP_SCOPE_ONELEVEL, Attrs, Name, SbtLiteral, L"distinguishedName=*");

		auto_free<LDAPMessage, &free_ldap_message> SearchResult;

		AwaitSearchResult(Lc, OpNum, SearchResult);

		LDAPMessage *Entry = ldap_first_entry(Lc->ld, SearchResult);

		while(Entry)
		{
			CppCancelOneHandlerExistance CCOHE;
			try
			{
				auto_free<PWSTR, free_ldap_value> DnValues = auto_take_(ldap_get_values(Lc->ld, Entry, L"distinguishedName"));

				if(!DnValues)
					RaiseTextError(L"Child of this object found with no distinguished name", NULL, Name);

				//TruncateDomainComponents(DnValues[0], Lc->DomainDc);

				NtdsObject Child(Lc, DnValues[0]);
				Dest.Proc(Child);
			}
			catch(CancelOneException)
			{}

			Entry = ldap_next_entry(Lc->ld, Entry);
		}
	}

	virtual BOOLEAN IsContainer() override
	{
		return TRUE;
	}

	virtual BOOLEAN AutoInhSupport() override
	{
		return TRUE;
	}
};

}//namespace Ldap